package middleware.p2p;

import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.xml.parsers.DocumentBuilderFactory;

import middleware.p2p.global.logger.GlobalLogger;
import middleware.p2p.global.logger.GlobalLoggerService;
import middleware.p2p.global.monitor.GlobalMonitor;
import middleware.p2p.global.monitor.GlobalMonitorService;
import middleware.p2p.log.MiddlewareLogLayout;
import middleware.p2p.log.MiddlewareMonitorLayout;
import middleware.p2p.log.MonitorFilter;
import middleware.p2p.log.VectorClock;

import org.apache.log4j.Appender;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.TTCCLayout;
import org.apache.log4j.varia.DenyAllFilter;
import org.apache.log4j.varia.LevelMatchFilter;
import org.apache.log4j.xml.XMLLayout;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *	La classe CORE si occupa di creare i componenti del sistema e di avviarli.
 */
public class CORE {
	/**
	 * 	Nome del logger incaricato del monitoring del contenuto della coda di Workflow
	 */
	public static final String monitorWFQueueName="middleware.p2p.monitor.wfqueue";
	/**
	 * 	Nome del logger incaricato del monitoring del contenuto della coda di TaskDescriptor
	 */
	public static final String monitorTDQueueName="middleware.p2p.monitor.tdqueue";
	/**
	 * 	Nome del logger incaricato del monitoring dello stato della coda di Workflow
	 */
	public static final String monitorWFQueueStatusName="middleware.p2p.monitor.wfqueue_status";
	/**
	 * 	Nome del logger incaricato del monitoring del contenuto della coda di TaskDescriptor
	 */
	public static final String monitorTDQueueStatusName="middleware.p2p.monitor.tdqueue_status";
	/**	
	 * 	Oggetto File che contiene le informazioni sul file di configurazione
	 */
	private File configFile;
	/**
	 * 	Worker Discovery del peer
	 */
	private static WorkerDiscovery disc;
	/**
	 * 	Vector clock legato al log dell'applicazione
	 */
	private static VectorClock logVclock;
	/**
	 * 	Vector clock legato al monitor dell'applicazione
	 */
	private static VectorClock monitorVclock;
	/**	
	 * 	Identificativo univoco del componente.
	 */
	private static String componentId;
	/**
	 * 	Frontend per le repliche
	 */
	private static ReplicaFrontend replicaFrontend;
	static {
		CORE.logVclock=new VectorClock();
		CORE.monitorVclock=new VectorClock();		
	}
	/**
	 * 	Costruttore di default della classe.
	 */
	public CORE(){
		Logger.getLogger("middleware.p2p").info("CORE created");
	}
	/**
	 * 	Metodo per impostare il File di configurazione.
	 * 	@param configFile	File da impostare come file di configurazione.
	 */
	public void setConfigFile(File configFile) {
		this.configFile = configFile;
	}
	/**
	 * 	Metodo per ottenere il file di configurazione utilizzato.
	 * 	@return	Il file di configurazione.
	 */
	public File getConfigFile() {
		return configFile;
	}
	/**
	 * @return the vclock
	 */
	public static VectorClock getLogVectorClock() {
		return logVclock;
	}
	/**
	 * @return the vclock
	 */
	public static VectorClock getMonitorVectorClock() {
		return monitorVclock;
	}
	/**
	 * @return the replicaFrontend
	 */
	public static ReplicaFrontend getReplicaFrontend() {
		return replicaFrontend;
	}
	/**
	 * 	@return the Worker Discovery
	 */
	public static WorkerDiscovery getWorkerDiscovery() {
		return disc;
	}
	/**
	 * @return the componentId
	 */
	public static String getComponentId() {
		return componentId;
	}
	/**
	 * 
	 */
	public static String generateComponentId(Address addr) {
		try {
			String tName="Component:"+addr.getIP()+":"+addr.getPort();
			MessageDigest md=MessageDigest.getInstance("MD5");
			md.update(tName.getBytes(),0,tName.length());				
			return new BigInteger(1,md.digest()).toString(16);
		} catch (NoSuchAlgorithmException nsae) {
			return "Component:"+addr.getIP()+":"+addr.getPort();
		}
	}
	/**
	 * 	Metodo che inizializza i componenti in base ai dati letti dal file di configurazione.
	 */
	public void init() {
		Logger log=Logger.getLogger(this.getClass());
		String architecture="N/A";
		String transport="N/A";
		String fileTransferPolicy="N/A";
		URL globalLoggerWsdl=null;
		URL globalMonitorWsdl=null;
		URL localCoordinatorWsdl=null;
		String thisPort=null;
		try {
			File file = new File("middleware_config.xml");
			Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file);
			doc.getDocumentElement().normalize();
			NodeList nodeLst = doc.getElementsByTagName("middleware");
			for (int i = 0; i < nodeLst.getLength(); i++) {
				Node tNode=nodeLst.item(i);
				if (tNode.getNodeType()==Node.ELEMENT_NODE) {
					Element config=(Element) tNode;
					Element archNode=(Element)(config.getElementsByTagName("architecture").item(0));
					architecture = (((Node)archNode.getChildNodes().item(0)).getNodeValue());
					Element transNode=(Element)(config.getElementsByTagName("transport").item(0));
					transport = (((Node)transNode.getChildNodes().item(0)).getNodeValue());
					Element fileNode=(Element)(config.getElementsByTagName("file_transfer_policy").item(0));
					fileTransferPolicy = (((Node)fileNode.getChildNodes().item(0)).getNodeValue());
					Element addrPort=(Element)(config.getElementsByTagName("replica_response_port").item(0));
					thisPort = (((Node)addrPort.getChildNodes().item(0)).getNodeValue());
					try {
						Element loggerWsdlNode=(Element)(config.getElementsByTagName("global_logger_wsdl").item(0));
						globalLoggerWsdl = new URL(((Node)loggerWsdlNode.getChildNodes().item(0)).getNodeValue());
						Element monitorWsdlNode=(Element)(config.getElementsByTagName("global_monitor_wsdl").item(0));
						globalMonitorWsdl = new URL(((Node)monitorWsdlNode.getChildNodes().item(0)).getNodeValue());
						Element coordinatorWsdlNode=(Element)(config.getElementsByTagName("local_coordinator_wsdl").item(0));
						localCoordinatorWsdl = new URL(((Node)coordinatorWsdlNode.getChildNodes().item(0)).getNodeValue());
					} catch (MalformedURLException mue) {
						mue.printStackTrace();
						log.error(mue,mue);
						System.exit(0);
					}
					
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error(e,e);
			System.exit(0);
		}
		Worker worker=new Worker();
		componentId=CORE.generateComponentId(worker.getAddr());			
		CORE.getLogVectorClock().setClock(componentId, 0);
		CORE.getMonitorVectorClock().setClock(componentId, 0);		
		if (globalLoggerWsdl!=null) {
			GlobalLoggerService serv=new GlobalLoggerService(globalLoggerWsdl);
			GlobalLogger globalLogger=serv.getGlobalLoggerPort();
			globalLogger.resetLoggingSession(CORE.getComponentId());
		}
		if (globalMonitorWsdl!=null) {
			GlobalMonitorService serv=new GlobalMonitorService(globalMonitorWsdl);
			GlobalMonitor globalMonitor=serv.getGlobalMonitorPort();
			globalMonitor.resetClock(CORE.getComponentId());
		}
		log.info("CORE initializated with following configuration:"+architecture+" "+transport+" "+fileTransferPolicy);		
		if ((architecture.equalsIgnoreCase("P2P"))&&(transport.equalsIgnoreCase("TCP"))&&(fileTransferPolicy.equalsIgnoreCase("PUSH"))) {
			disc=new WorkerDiscovery();
			//Codice per leggere gli ip da file
			try {
				File file = new File("discovery_peers.xml");
				Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file);
				doc.getDocumentElement().normalize();
				NodeList nodeLst = doc.getElementsByTagName("peer");
				for (int i = 0; i < nodeLst.getLength(); i++) {
					Node tNode=nodeLst.item(i);
					Address addr=new Address();
					if (tNode.getNodeType()==Node.ELEMENT_NODE) {
						Element config=(Element) tNode;
						Element peerIp=(Element)(config.getElementsByTagName("ip").item(0));
						addr.setIP(((Node)peerIp.getChildNodes().item(0)).getNodeValue());
						Element peerPort=(Element)(config.getElementsByTagName("port").item(0));
						addr.setPort(Integer.parseInt(((Node)peerPort.getChildNodes().item(0)).getNodeValue()));
						disc.addWorker(new WorkerDescriptor(addr));
						CORE.getLogVectorClock().setClock(CORE.generateComponentId(addr), 0);
						CORE.getMonitorVectorClock().setClock(CORE.generateComponentId(addr), 0);
						Element peerRMWsdl=(Element)(config.getElementsByTagName("rm_wsdl").item(0));
						try {
							disc.registerRM(CORE.generateComponentId(addr), new URL(peerRMWsdl.getChildNodes().item(0).getNodeValue()));
						} catch (MalformedURLException mue) {
						}
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
				log.error(e,e);
				System.exit(0);
			}
			CORE.replicaFrontend=new ReplicaFrontend(worker.getAddr().getIP()+":"+thisPort,localCoordinatorWsdl);
			WFQueueManager wfQueue=new WFQueueManager();
			UI ui=new UI(wfQueue);			
			Manager manager=new Manager(wfQueue, disc);			
			try {				
				Appender mwAppender=new FileAppender(new MiddlewareLogLayout(componentId,globalLoggerWsdl), "middleware_local.log",true);				
				LevelMatchFilter flt=new LevelMatchFilter();
				flt.setAcceptOnMatch(true);
				flt.setLevelToMatch(Level.INFO.toString());
				mwAppender.addFilter(flt);
				mwAppender.addFilter(new DenyAllFilter());
				Appender wfQueueContentAppender=new FileAppender(new MiddlewareMonitorLayout(),"middleware_wfqueue_content.log",true);
				wfQueueContentAppender.addFilter(new MonitorFilter(CORE.monitorWFQueueName));
				wfQueueContentAppender.addFilter(new DenyAllFilter());
				Appender wfQueueStatusAppender=new FileAppender(new MiddlewareMonitorLayout(globalMonitorWsdl),"middleware_wfqueue_status.log",true);
				wfQueueStatusAppender.addFilter(new MonitorFilter(CORE.monitorWFQueueStatusName));
				wfQueueStatusAppender.addFilter(new DenyAllFilter());
				Appender tQueueContentAppender=new FileAppender(new MiddlewareMonitorLayout(),"middleware_tqueue_content.log",true);
				tQueueContentAppender.addFilter(new MonitorFilter(CORE.monitorTDQueueName));
				tQueueContentAppender.addFilter(new DenyAllFilter());
				Appender tQueueStatusAppender=new FileAppender(new MiddlewareMonitorLayout(globalMonitorWsdl),"middleware_tqueue_status.log",true);
				tQueueStatusAppender.addFilter(new MonitorFilter(CORE.monitorTDQueueStatusName));
				tQueueStatusAppender.addFilter(new DenyAllFilter());
				BasicConfigurator.configure(mwAppender);
				BasicConfigurator.configure(wfQueueContentAppender);
				BasicConfigurator.configure(wfQueueStatusAppender);
				BasicConfigurator.configure(tQueueContentAppender);
				BasicConfigurator.configure(tQueueStatusAppender);
			} catch (IOException ioe) {
			}
			worker.start();
			ui.start();
			manager.start();
			try {
				worker.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			log.error("Unable to start with this configuration.");
		}
	}
	public static void main(String args[]) {
		//Codice per la gestione del logging
		boolean logOn=true;
		boolean debug=false;
		for (int i=0;i<args.length;i++) {
			if (args[i].equalsIgnoreCase("--DEBUG")) {	
				debug=true;
			} else if(args[i].equalsIgnoreCase("--NOLOG")) {
				logOn=false;
			} 
		}
		System.out.println("Application Startup Parameter:");
		System.out.println("LOG: "+((logOn)?"ON":"OFF"));
		System.out.println("DEBUG: "+((debug)?"ON":"OFF"));
		System.out.println("Use --DEBUG parameter to enable printing on stdout and --NOLOG to disable logging");
		Logger.getLogger("middleware.p2p");
		if (debug) {
			BasicConfigurator.configure();
		}
		if (logOn) {
			try {
				FileAppender outFile=new FileAppender(new TTCCLayout("dd-MM-yyyy HH:mm:ss,SSS"),"middleware_p2p.out",true);
				BasicConfigurator.configure(outFile);
				FileAppender xmlFile=new FileAppender(new XMLLayout(), "middleware_p2p.log", true);
				BasicConfigurator.configure(xmlFile);
			} catch (IOException e) {
				e.printStackTrace();			
			}
		}
		//Create Monitor Logger
		Logger.getLogger(CORE.monitorWFQueueName);
		Logger.getLogger(CORE.monitorWFQueueStatusName);
		Logger.getLogger(CORE.monitorTDQueueName);
		Logger.getLogger(CORE.monitorTDQueueStatusName);
		Logger log= Logger.getLogger("middleware.p2p");		
		log.debug("Begin Logging Session");
		CORE core= new CORE();
		core.init();
	}
}
